package com.qinchen.spring;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author 钦尘
 * @date 2021/9/30 5:53
 * @description TODO
 */
public class ApplicationContext {

    public static final String SCOPE_SINGLETON = "singleton";
    private Class configClass;

    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>();

    private Map<String, Object> singletonObjects = new HashMap<String, Object>();

    /**
     * BeanPostProcessor 缓存 ，实际为 linked list
     */
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    /**
     * 核心逻辑的开始
     *
     * @param configClass
     */
    public ApplicationContext(Class configClass) {
        this.configClass = configClass;

        // 根据扫描路径，扫描 bean，得到bean定义map
        scan(configClass);

        // 遍历bean定义，准备创建bean
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            // 如果bean的作用域为单例，则创建（这里为了简单，暂时没考虑懒加载bean）
            if (SCOPE_SINGLETON.equals(beanDefinition.getScope())) {
                // 创建好bean，并将其放入bean的单例池Map中，以beanName为key
                Object bean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, bean);
            }
        }
    }

    /**
     * 根据扫描路径，扫描 bean，最终形成bean定义对象放入Map中缓存
     *
     * @param configClass 应用配置类
     */
    private void scan(Class configClass) {
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            // 得到扫描路径
            ComponentScan componentScan = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String path = componentScan.value();
            System.out.println("得到扫描路径:" + path);

            // 包路径转文件路径
            path = path.replace(".", "/");
            // 得到应用程序类加载器，通过类加载器可以得到该路径下的所有类文件
            ClassLoader classLoader = ApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(path);
            File file = new File(resource.getFile());
            if (file.isDirectory()) {
                // 遍历扫描包路径下的所有类文件（这里为了简单，只考虑了一级目录）
                for (File classFile : file.listFiles()) {
                    String absolutePath = classFile.getAbsolutePath();
                    System.out.println("得到类的绝对路径：" + absolutePath);
                    // 通过绝对路径，得到类完整的包路径，后面方便通过类加载器加载该类，得到Class对象（为了简单，下面进行简单截取和替换处理）
                    absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
                    absolutePath = absolutePath.replace(File.separator, ".");
                    System.out.println("处理后的类完整路径 = " + absolutePath);

                    try {
                        // 通过应用程序类加载器+类的完整路径，加载该类
                        Class<?> clazz = classLoader.loadClass(absolutePath);
                        // 判断类是否有 Component 注解修饰
                        if (clazz.isAnnotationPresent(Component.class)) {

                            // 扩展点设计：判断当前 class是否实现了 BeanPostProcessor 接口
                            if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                                BeanPostProcessor instance = (BeanPostProcessor) clazz.getConstructor().newInstance();
                                // 该bean实现了bean的后置处理器接口，则将其实例转成接口类型先缓存起来，供后面使用
                                // 实际上可以理解为这是一种特殊的 bean，后面将有很大作用
                                beanPostProcessorList.add(instance);
                            }
                            // 通过注解信息，得到bean名称，若没有获取到，则自动生成（以类名称首字母小写）
                            Component annotation = clazz.getAnnotation(Component.class);
                            String beanName = annotation.value();
                            if ("".equals(beanName)) {
                                // 自动生成bean名称（Spring中真实实现）
                                beanName = Introspector.decapitalize(clazz.getSimpleName());
                            }

                            // 有了bean名称后，先要创建该 bean 的定义，并放入Map中缓存起来
                            BeanDefinition beanDefinition = new BeanDefinition();
                            // 设置bean的类型
                            beanDefinition.setType(clazz);
                            // 若该类有Scope修饰，则取出Scope对于的value，若无修饰，则默认为单例bean
                            if (clazz.isAnnotationPresent(Scope.class)) {
                                // 判断bean是否是单例的？prototype|singleton
                                Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
                                String scope = scopeAnnotation.value();
                                beanDefinition.setScope(scope);
                                if (scope == "prototype") {
                                    // 原型模式，特殊逻辑处理
                                } else {
                                    // 单例模式，特殊逻辑处理
                                }
                            } else {
                                // 默认为：单例
                                beanDefinition.setScope(SCOPE_SINGLETON);
                            }
                            // bean 定义放入Map中缓存起来，键则为 bean 的名称
                            beanDefinitionMap.put(beanName, beanDefinition);
                        }
                    } catch (ClassNotFoundException | NoSuchMethodException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 创建bean
     *
     * @param beanName
     * @param beanDefinition
     * @return
     */
    private Object createBean(String beanName, BeanDefinition beanDefinition) {

        Class clazz = beanDefinition.getType();
        Object instance = null;
        try {
            // TODO 这里默认使用无参构造创建对象，也可考虑有参构造，思考如何实现？
            instance = clazz.getConstructor().newInstance();

            // 接下来给其内部属性赋值，看属性是否有Autowired注解，有则给属性赋值，思考值从何来？
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                boolean annotationPresent = field.isAnnotationPresent(Autowired.class);
                if (annotationPresent) {
                    // 关闭语言访问安全检查，可以达到提升反射速度
                    field.setAccessible(true);
                    // 这里Spring中的实际实现：先根据类型从容器中找到对应bean并赋值，如果找到多个再根据属性名称匹配
                    Class<?> type = field.getType();
                    System.out.println("这里得到字段" + field.getName() + "的类型为" + type.getName());
                    // 这里我们简单处理，直接拿字段名字去获取Bean
                    Object fieldBean = getBean(field.getName());
                    field.set(instance, fieldBean);
                }
            }

            // 扩展点：Aware 回调，操作是在初始化前调用的，一般用于得到spring初始化bean过程中的一些信息，如bean的实际名称
            if (instance instanceof BeanNameAware) {
                // 这里模拟，如果该bean实现了BeanNameAware接口，则在初始化bean之前，先回调其setBeanName方法
                ((BeanNameAware) instance).setBeanName(beanName);
            }

            // 扩展点：调用初始化前的自定义 BeanPostProcessor 逻辑，回顾前面创建bean定义时缓存的所有bean后置处理器，将当前bean和名称传入遍历执行
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                // TODO 思考，这里是Spring中实现什么的关键设计点？
                instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
            }

            // 扩展点：初始化处理，可以在初始化即将结束前，最后进行一些特殊属性设置处理的扩展点
            if (instance instanceof InitializingBean) {
                ((InitializingBean) instance).afterPropertiesSet();
            }

            // 扩展点：调用初始化后的自定义 BeanPostProcessor 逻辑，到这里意味着bean已经成型了，可做最后特殊修饰
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
            }
            System.out.println(beanName + "的创建已完成");
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        return instance;
    }

    /**
     * 通过 beanName 获取bean实例
     *
     * @param beanName 名称
     * @return bean
     */
    public Object getBean(String beanName) {

        // bean定义Map中若不包含传入的bean名称，则抛出异常（这里简单处理）
        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new RuntimeException("beanName not exist");
        }

        // 得到对应的bean定义
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        // 获取其作用域
        if (SCOPE_SINGLETON.equals(beanDefinition.getScope())) {
            // 如果是单例bean，则直接从单例池中获取
            Object singletonObject = singletonObjects.get(beanName);
            // TODO 这里可能从单例池中得不到bean，思考在什么场景下？这里还存在什么问题？
            if (singletonObject == null) {
                // 临时去创建，根据当前传入的bean名称和对应的bean定义对象
                singletonObject = createBean(beanName, beanDefinition);
                // 创建好后返回，同时也放入单例池中
                singletonObjects.put(beanName, singletonObject);
            }
            return singletonObject;
        } else {
            // 如果是原型bean，则创建一个全新的 bean 返回
            Object prototypeBean = createBean(beanName, beanDefinition);
            return prototypeBean;
        }
    }
}
